//
// Created by Jinyu Zhu on 2022/4/19.
//

#ifndef PROVDB_TAG_GRAPH_GRAPHD_H
#define PROVDB_TAG_GRAPH_GRAPHD_H

#include <utility>

#include "string"
#include "vector"
#include "map"
#include "algorithm"
#include "outer.h"

#include "basic.h"

// pre definitions
class DirectedGraph;
class DGNodeI;
class DGEdgeI;

typedef TPt<DirectedGraph> PDirectedGraph;

// Directed (Multi-) Graph
class DirectedGraph {
public:
    class Node {
    private:
        int Id; std::string Name;
        long TimeStamp; double Value;
        std::vector<int> InEIdV, OutEIdV;

    public:
        Node(const int &Id_, std::string Name_, const long& TimeStamp_=0, const double& Value_ = 0) :
            Id(Id_), Name(std::move(Name_)), TimeStamp(TimeStamp_), Value(Value_), InEIdV(), OutEIdV() {};
        Node(const Node& N) = default;
        explicit Node(FIn &fIn);

    public:
        void Save(FOut &fOut) const;

    public:
        int GetDeg() const { return (int) InEIdV.size() + (int) OutEIdV.size(); }
        int GetInDeg() const { return (int) InEIdV.size(); }
        int GetOutDeg() const { return (int) OutEIdV.size(); }

    private:
        void InsInEIdVSorted(const int &item) { sorted_insert(InEIdV, item); }
        void InsOutEIdVSorted(const int &item) { sorted_insert(OutEIdV, item); }
        bool BinSearchBinInEIdV(const int &item) const { return std::binary_search(InEIdV.cbegin(), InEIdV.cend(), item); }
        bool BinSearchBinOutEIdV(const int &item) const { return std::binary_search(OutEIdV.cbegin(), OutEIdV.cend(), item); }

        int GetInEId(const int& idx) { return InEIdV.at(idx); }
        int GetOutEId(const int& idx) { return OutEIdV.at(idx); }

    public:
        friend class DirectedGraph;
        friend class AlgorithmDirectedGraph;
        friend class DGNodeI;
    };

    class Edge {
    private:
        int Id, SrcNId, DstNId;
        std::string RelationType, TsvId;
        long TimeStamp;
    public:
        Edge(): Id(-1), SrcNId(-1), DstNId(-1), RelationType(), TsvId(), TimeStamp(0) {}
        Edge(const int& Id_, const int& SrcNId_, const int& DstNId_, std::string  RelationType_, const long& TimeStamp_, std::string TsvId_="") :
             Id(Id_), SrcNId(SrcNId_), DstNId(DstNId_), RelationType(std::move(RelationType_)), TsvId(std::move(TsvId_)), TimeStamp(TimeStamp_) {}
        Edge(const Edge& E) = default;
        explicit Edge(FIn &fIn);

    public:
        void Save(FOut &fOut) const;

    public:
        int GetId() const { return Id; }
        int GetSrcNId() const { return SrcNId; }
        int GetDstNId() const { return DstNId; }
        std::string GetRelationType() const { return RelationType; }
        std::string GetTsvId() const { return TsvId; }
        long GetTimeStamp() const { return TimeStamp; }

    public:
        friend class DirectedGraph;
        friend class AlgorithmDirectedGraph;
        friend class DGEdgeI;
    };

private:
    TCRef CRef;
    int MaxNId, MaxEId;
    // node main map
    std::map<int, Node> NodeMap;
    std::map<int, Edge> EdgeMap;
    // name index
    std::map<std::string, std::vector<int> > NodeIndex;

// Iterators
public:
    DGNodeI BeginNodeI();
    DGNodeI EndNodeI();
    DGNodeI GetNodeNIdI(const int& NId);

    DGEdgeI BeginEdgeI();
    DGEdgeI EndEdgeI();
    DGEdgeI GetEdgeNIdI(const int& EId);

private:
    void SaveNodeMap(FOut &fout) { save_map_basic(NodeMap, fout); }
    void LoadNodeMap(FIn &fin) { load_map_basic(NodeMap, fin); }

    void SaveEdgeMap(FOut &fout) { save_map_basic(EdgeMap, fout); }
    void LoadEdgeMap(FIn &fin) { load_map_basic(EdgeMap, fin); }

public:
    DirectedGraph(): CRef(), MaxNId(0), MaxEId(0), NodeMap(), EdgeMap(), NodeIndex() {};

    explicit DirectedGraph(const std::string &LoadFilePath): CRef(), MaxNId(0), MaxEId(0), NodeMap(), EdgeMap(), NodeIndex() {
        FIn fin(LoadFilePath); fin.Load(MaxNId); fin.Load(MaxEId);
        LoadNodeMap(fin); LoadEdgeMap(fin); fin.Load(NodeIndex);
    }

    DirectedGraph(const DirectedGraph& D): CRef(), MaxNId(D.MaxNId), MaxEId(D.MaxEId), NodeMap(D.NodeMap), EdgeMap(D.EdgeMap), NodeIndex(D.NodeIndex) {};

// Use with smart pointer
public:
    static PDirectedGraph New() { return new DirectedGraph(); }

    static PDirectedGraph Load(const std::string &LoadFilePath) { return new DirectedGraph(LoadFilePath); }

    PDirectedGraph MakeCopy() { return new DirectedGraph(*this); }

public:
    void Save(const std::string &SaveFilePath) {
        FOut fOut(SaveFilePath); fOut.Save(MaxNId); fOut.Save(MaxEId);
        SaveNodeMap(fOut); SaveEdgeMap(fOut); fOut.Save(NodeIndex);
    }

private:
    Node &GetNode(const int &Id) { return NodeMap.at(Id); }
    Edge &GetEdge(const int &EId) { return EdgeMap.at(EId); }

    Node *GetNodeP(const int& Id) { return &(NodeMap.at(Id)); }
    Edge *GetEdgeP(const int &EId) { return &(EdgeMap.at(EId)); }

    // copy Node to this, return node_id if possible, -1 if fail.
    int AddNodeReplica(Node* NodeP);
    int AddEdgeReplica(Edge* EdgeP);
public:
    DirectedGraph &operator=(const DirectedGraph &Graph) {
        if (this != &Graph) {
            MaxNId = Graph.MaxNId; MaxEId = Graph.MaxEId;
            NodeMap = Graph.NodeMap; EdgeMap = Graph.EdgeMap;
            NodeIndex = Graph.NodeIndex;
        }
        return *this;
    }

    bool IsNode(const int &Id) { return NodeMap.find(Id) != NodeMap.end(); }
    bool IsEdge(const int &EId) { return EdgeMap.find(EId) != EdgeMap.end(); }
    // return true if there is one edge between src and dst
    bool IsEdge(const int& SrcNId, const int& DstNId);

    int AddNode(const std::string &Name, const long& TimeStamp_=0, const double& Value_ = 0);
    int AddEdge(const int &SrcNId, const int &DstNId, const std::string& RelationType, const long& TimeStamp_, const std::string& TsvId_="");

    // return vec<int> if found, empty vector if not found
    std::vector<int> GetNodeId(const std::string &Name) {
        return (NodeIndex.find(Name) != NodeIndex.end()) ? NodeIndex.at(Name): std::vector<int>();
    }
    // return 0 if not exists, else num_count
    int NameNodeNum(const std::string &Name) { return (NodeIndex.find(Name) != NodeIndex.end()) ? (int) NodeIndex.at(Name).size() : 0; }

    // return old node id if success, -1 if not found
    int DelNode(const int& NId);
    // return old edge id if success, -1 if not found
    int DelEdge(const int& EId);

    // return brief info
    std::string BriefInfo();

public:
    friend class TPt<DirectedGraph>;
    friend class AlgorithmDirectedGraph;
    friend class DGNodeI;
    friend class DGEdgeI;
};

// Iterators
class DGNodeI{
public:
    typedef std::map<int, DirectedGraph::Node>::iterator Iter;

private:
    Iter iter;
    const DirectedGraph* graph;

public:
    DGNodeI(): iter(), graph(nullptr) {}
    DGNodeI(const Iter& DGNodeIter, const DirectedGraph* Graph): iter(DGNodeIter), graph(Graph) {}
    DGNodeI(const DGNodeI& NodeI) = default;

    DGNodeI& operator= (const DGNodeI& NodeI) { iter = NodeI.iter; graph = NodeI.graph; return *this; }
    bool operator == (const DGNodeI& NodeI) const { return iter == NodeI.iter; }
    bool operator != (const DGNodeI& NodeI) const { return iter != NodeI.iter; }

    // Increment iterator.
    DGNodeI& operator++ (int) { iter++; return *this; }
    void Next() { iter++; }

    // Getters and Setters
    int GetId() const { return (iter->second).Id; }
    std::string GetName() const { return (iter->second).Name; }
    long GetTimeStamp() const { return (iter->second).TimeStamp; }
    double GetValue() const { return (iter->second).Value; }

    void SetName(const std::string& Name) { (iter->second).Name = Name; }
    void SetTimeStamp(const long& TimeStamp) { (iter->second).TimeStamp = TimeStamp; }
    void SetValue(const double& Value) { (iter->second).Value = Value; }

    int GetOutDeg() const { return (int) (iter->second).GetOutDeg(); }
    int GetInDeg() const { return (int) (iter->second).GetInDeg(); }
    int GetDeg() const { return (int) (iter->second).GetDeg(); }

    // Nth Edge's id
    int GetNthOutEdgeId(const int& EdgeN) const { return (iter->second).OutEIdV.at(EdgeN); }
    int GetNthInEdgeId(const int& EdgeN) const { return (iter->second).InEIdV.at(EdgeN); }

    // Nth Edge's neighbor node id
    int GetNthOutNbrId(const int& EdgeN) const { return graph->EdgeMap.at((iter->second).OutEIdV.at(EdgeN)).GetDstNId(); }
    int GetNthInNbrId(const int& EdgeN) const { return graph->EdgeMap.at((iter->second).InEIdV.at(EdgeN)).GetSrcNId(); }

};

class DGEdgeI{
public:
    typedef std::map<int, DirectedGraph::Edge>::iterator Iter;

private:
    Iter iter;
    const DirectedGraph* graph;

public:
    DGEdgeI(): iter(), graph(nullptr) {}
    DGEdgeI(const Iter& DGEdgeIter, const DirectedGraph* Graph): iter(DGEdgeIter), graph(Graph) {}
    DGEdgeI(const DGEdgeI& EdgeI) = default;

    bool operator== (const DGEdgeI& EdgeI) const { return iter == EdgeI.iter; }
    bool operator!= (const DGEdgeI& EdgeI) const { return iter != EdgeI.iter; }
    DGEdgeI& operator= (const DGEdgeI& EdgeI) { if (this != &EdgeI) { iter = EdgeI.iter; graph = EdgeI.graph; } return *this; }

    // Increment iterator.
    DGEdgeI& operator++ (int) { iter++; return *this; }

    void Next() { iter++; }

    // Getters and Setters
    int GetId() { return (iter->second).Id; }
    int GetSrcNId() { return (iter->second).SrcNId; }
    int GetDstNId() { return (iter->second).DstNId; }
    std::string GetRelationType() { return (iter->second).RelationType; }
    std::string GetTsvId() { return (iter->second).TsvId; }
    long GetTimeStamp() { return (iter->second).TimeStamp; }
};


#endif //PROVDB_TAG_GRAPH_GRAPHD_H